#include <mach/board_config.h>
#include <mach/pinctrl.h>
#include <linux/module.h>
#include <mach/io.h>
#include <mach/pmu.h>
#include "iopad.h"
#include "chip.h"

/* #define  FH_PINCTRL_DEBUG */
#ifdef FH_PINCTRL_DEBUG
#define PRINT_DBG(fmt,args...)  OS_PRINT(fmt,##args)
#else
#define PRINT_DBG(fmt,args...)  do{} while(0)
#endif

static struct
{
	void *vbase;
	void *pbase;
	PinCtrl_Pin * pinlist[PAD_NUM];
} pinctrl_obj;
OS_LIST fh_pinctrl_devices = OS_LIST_INIT(fh_pinctrl_devices);

static void fh_pinctrl_check_duplicate_pin(PinCtrl_Pin *pin, int start_pad)
{
	int i;
	PinCtrl_Pin *p;
	if (!pin)
		return;
	for (i = start_pad; i < PAD_NUM; i++) {
		p = pinctrl_obj.pinlist[i];
		if (p && p != pin && !strcmp(pin->func_name, p->func_name)) {
			OS_PRINT("WARNING: %s already exists in pad %d\n",
					p->func_name, p->pad_id);
		}
	}
}

static int fh_pinctrl_func_select(PinCtrl_Pin *pin, unsigned int flag)
{
	unsigned int reg;

	if (!pin)
	{
		OS_PRINT("ERROR: pin is null\n\n");
		return -1;
	}

	if (flag & NEED_CHECK_PINLIST)
	{
		if (pinctrl_obj.pinlist[pin->pad_id])
		{
			OS_PRINT("ERROR: pad %d has already been set\n\n", pin->pad_id);
			return -2;
		}
	}
	fh_pinctrl_check_duplicate_pin(pin, 0);

	reg = GET_REG(pinctrl_obj.vbase + pin->reg_offset);

	pin->reg = (PinCtrl_Register *)&reg;

	pin->reg->bit.mfs = pin->func_sel;

	if (pin->pullup_pulldown == PUPD_DOWN) {
		pin->reg->bit.pun = PUPD_DISABLE;
		pin->reg->bit.pdn = PUPD_ENABLE;
	}
	else if(pin->pullup_pulldown == PUPD_UP) {
		pin->reg->bit.pun = PUPD_ENABLE;
		pin->reg->bit.pdn = PUPD_DISABLE;
	} else {
		pin->reg->bit.pdn = PUPD_ZERO;
		pin->reg->bit.pun = PUPD_ZERO;
	}

	pin->reg->bit.ds = pin->driving_curr;
	pin->reg->bit.st = 1;

	pin->reg->bit.ie = 1;
#if defined(CONFIG_ARCH_FH8636_FH8852V20X)
	/* ie = 0 for mipi pins */
	if (strncmp(pin->func_name, "MIPI", 4) == 0)
		pin->reg->bit.ie = 0;
#endif
	pin->reg->bit.oe = pin->output_enable;

	SET_REG(pinctrl_obj.vbase + pin->reg_offset, pin->reg->dw);

	pinctrl_obj.pinlist[pin->pad_id] = pin;

	return 0;
}

static int fh_pinctrl_mux_switch(PinCtrl_Mux *mux, unsigned int flag)
{
	if (mux->cur_pin >= mux->mux_num) {
		OS_PRINT("ERROR: selected function is not exist, sel_func=%d\n\n", mux->cur_pin);
		return -3;
	}

	if (!mux->mux_pin[mux->cur_pin]) {
		OS_PRINT("ERROR: %s mux->mux_pin[%d] has no pin\n\n",
				mux->mux_pin[0]->func_name, mux->cur_pin);
		return -4;
	}

	PRINT_DBG("\t%s[%d]\n", mux->mux_pin[mux->cur_pin]->func_name, mux->cur_pin);
	return fh_pinctrl_func_select(mux->mux_pin[mux->cur_pin], flag);
}


static int fh_pinctrl_device_switch(PinCtrl_Device *dev, unsigned int flag)
{
	int i, ret;

	for (i = 0; i < dev->mux_count; i++) {
		unsigned int *mux_addr = (unsigned int *)((unsigned int)dev
				+ sizeof(*dev) - 4 + i*4);
		PinCtrl_Mux *mux = (PinCtrl_Mux *)(*mux_addr);

		ret = fh_pinctrl_mux_switch(mux, flag);
		if (ret)
			return ret;
	}

#ifdef SD1_FUNC_SEL_MAP
	if (strncmp(dev->dev_name, "SD1", 3) == 0) {
		PinCtrl_Mux *mux = (PinCtrl_Mux *)dev->mux;

		/* for some platform, set SD1_FUNC_SEL after all mux switch */
		fh_pmu_set_sdc1_funcsel(mux->cur_pin);
	}
#endif
	return 0;
}

static PinCtrl_Device * fh_pinctrl_get_device_by_name(char *name)
{
	PinCtrl_Device *dev = OS_NULL;

	list_for_each_entry(dev, &fh_pinctrl_devices, list)
	{
		if (!strcmp(name, dev->dev_name))
		{
			return dev;
		}
	}

	return 0;
}

static PinCtrl_Mux *fh_pinctrl_get_mux_by_name(char *mux_name)
{
	int i;
	PinCtrl_Device *dev;

	list_for_each_entry(dev, &fh_pinctrl_devices, list)	{
		for (i = 0; i < dev->mux_count; i++) {
			unsigned int *mux_addr = (unsigned int *)(
				(unsigned int)dev + sizeof(*dev) - 4 + i*4);
			PinCtrl_Mux *mux = (PinCtrl_Mux *)(*mux_addr);

			if (!strcmp(mux_name, mux->mux_pin[0]->func_name))
				return mux;
		}
	}

	return NULL;
}

int fh_pinctrl_check_pinlist(void)
{
	int i;

	for (i=0; i<PAD_NUM; i++) {
		if (!pinctrl_obj.pinlist[i])
			PRINT_DBG("ERROR: pad %d is still empty\n", i);
	}

	return 0;
}

static int fh_pinctrl_init_devices(char **devlist,
		int listsize, unsigned int flag)
{
	int i, ret;
	PinCtrl_Device *dev;

	memset(pinctrl_obj.pinlist, 0, sizeof(pinctrl_obj.pinlist));

	for (i = 0; i < listsize; i++)
	{
		if (!devlist[i])
			continue;
		dev = fh_pinctrl_get_device_by_name(devlist[i]);

		if (!dev) {
			OS_PRINT("ERROR: cannot find device %s\n", devlist[i]);
			return -5;
		}

		PRINT_DBG("%s:\n", dev->dev_name);
		ret = fh_pinctrl_device_switch(dev, flag);
		PRINT_DBG("\n");
		if (ret)
			return ret;

	}

	fh_pinctrl_check_pinlist();

	return 0;

}

static void fh_pinctrl_init_pin(void)
{
	int i;

	for (i = 0; i < PAD_NUM; i++)
	{
		PinCtrl_Pin *pin = pinctrl_obj.pinlist[i];

		if (!pin) {
			PinCtrl_Register reg;

			PRINT_DBG("ERROR: pad %d is empty\n", i);
			reg.dw = GET_REG(pinctrl_obj.vbase + i * 4);
			reg.bit.oe = 0;
			SET_REG(pinctrl_obj.vbase + i * 4, reg.dw);
			continue;
		}
		pin->reg->dw = GET_REG(pinctrl_obj.vbase +
				pin->reg_offset);

		pin->input_enable = pin->reg->bit.ie;
		pin->output_enable = pin->reg->bit.oe;
	}
}

void fh_pinctrl_init(unsigned int base)
{
	pinctrl_obj.vbase = pinctrl_obj.pbase = (void *)base;

	fh_pinctrl_init_devicelist(&fh_pinctrl_devices);
	fh_pinctrl_init_devices(fh_pinctrl_selected_devices,
			ARRAY_SIZE(fh_pinctrl_selected_devices),
			NEED_CHECK_PINLIST);
	fh_pinctrl_init_pin();
}

void fh_pinctrl_prt(struct seq_file *sfile)
{
	int i;
	unsigned int base = (unsigned int)pinctrl_obj.vbase & 0xfff;
	static char *pupds[3] = {
		[PUPD_NONE] = "none",
		[PUPD_UP] = "up",
		[PUPD_DOWN] = "down",
	};

	seq_printf(sfile, "%2s\t%8s\t%4s\t%8s\t%4s\t%4s\t%4s\t%4s\t%4s\n",
			"id", "name", "addr", "reg", "sel", "ie", "oe", "pupd", "ds");
	for (i = 0; i < PAD_NUM; i++) {
		if (!pinctrl_obj.pinlist[i]) {
			OS_PRINT("ERROR: pad %d is empty\n", i);
			continue;
		}
		fh_pinctrl_check_duplicate_pin(pinctrl_obj.pinlist[i], i+1);
		seq_printf(sfile, "%02d\t%8s\t0x%04x\t0x%08x\t%04d\t%04d\t%04d\t%4s\t%04d\n",
				pinctrl_obj.pinlist[i]->pad_id,
				pinctrl_obj.pinlist[i]->func_name,
				pinctrl_obj.pinlist[i]->reg_offset + base,
				GET_REG(pinctrl_obj.vbase + pinctrl_obj.pinlist[i]->reg_offset),
				pinctrl_obj.pinlist[i]->func_sel,
				pinctrl_obj.pinlist[i]->input_enable,
				pinctrl_obj.pinlist[i]->output_enable,
				pupds[pinctrl_obj.pinlist[i]->pullup_pulldown],
				pinctrl_obj.pinlist[i]->driving_curr);
	}

}
EXPORT_SYMBOL(fh_pinctrl_prt);


int fh_pinctrl_smux(char *devname, char* muxname, int muxsel, unsigned int flag)
{
	PinCtrl_Device *dev;
	int i, ret;
	PinCtrl_Mux *foundmux = NULL;
	char *oldfunc = NULL;

	if (flag & PIN_RESTORE) {
		foundmux = fh_pinctrl_get_mux_by_name(muxname);
		if (foundmux == NULL) {
			OS_PRINT("ERROR: PIN_RESTORE, cannot found mux: %s\n",
				muxname);
			return -10;
		}
		goto mux_switch;
	}

	dev = fh_pinctrl_get_device_by_name(devname);

	if (!dev) {
		OS_PRINT("ERROR: cannot find device %s\n", devname);
		return -4;
	}

	for (i = 0; i < dev->mux_count; i++)	{
		unsigned int *mux_addr = (unsigned int *)((unsigned int)dev
				+ sizeof(*dev) - 4 + i*4);
		PinCtrl_Mux *mux = (PinCtrl_Mux *)(*mux_addr);

		if (!strcmp(muxname, mux->mux_pin[0]->func_name)) {
			foundmux = mux;
			mux->cur_pin = muxsel;
			goto mux_switch;
		}
	}

	if (i == dev->mux_count) {
		OS_PRINT("ERROR: cannot find mux %s of device %s\n",
			muxname, devname);
		return -6;
	}

mux_switch:
	if (flag & PIN_BACKUP) {
		int id = foundmux->mux_pin[foundmux->cur_pin]->pad_id;
		PinCtrl_Pin *pin = pinctrl_obj.pinlist[id];

		if (pin == NULL) {
			OS_PRINT("ERROR: PIN_BACKUP, oldpin is null\n");
			return 0;
		}
		oldfunc = pin->func_name;
	}
	ret = fh_pinctrl_mux_switch(foundmux, flag);
	if (flag & PIN_BACKUP)
		ret = (int)oldfunc;

	fh_pinctrl_check_pinlist();

	return ret;
}
EXPORT_SYMBOL(fh_pinctrl_smux);

char *fh_pinctrl_smux_backup(char *devname, char *muxname, int muxsel)
{
	return (char *)fh_pinctrl_smux(devname, muxname, muxsel, PIN_BACKUP);
}

char *fh_pinctrl_smux_restore(char *devname, char *muxname, int muxsel)
{
	return (char *)fh_pinctrl_smux(devname, muxname, muxsel, PIN_RESTORE);
}

EXPORT_SYMBOL(fh_pinctrl_smux_backup);
EXPORT_SYMBOL(fh_pinctrl_smux_restore);


int fh_pinctrl_sdev(char *devname, unsigned int flag)
{
	PinCtrl_Device *dev;
	int ret;

	dev = fh_pinctrl_get_device_by_name(devname);
	if (!dev) {
		OS_PRINT("ERROR: cannot find device %s\n", devname);
		return -7;
	}

	OS_PRINT("%s:\n", dev->dev_name);
	ret = fh_pinctrl_device_switch(dev, flag);
	OS_PRINT("\n");
	if (ret)
		return ret;

	fh_pinctrl_check_pinlist();

	return 0;
}
EXPORT_SYMBOL(fh_pinctrl_sdev);

static PinCtrl_Pin *fh_pinctrl_get_pin_by_name(char *pin_name)
{
	int i;
	PinCtrl_Pin *pin = NULL;

	for (i = 0; i < PAD_NUM; i++) {
		pin = pinctrl_obj.pinlist[i];
		if (!pin || !pin->func_name)
			continue;
		if (!strcmp(pin->func_name, pin_name))
			return pin;
	}

	return NULL;
}

int fh_pinctrl_spupd(char *pin_name, unsigned int pupd)
{
	PinCtrl_Pin *pin = NULL;

	pin = fh_pinctrl_get_pin_by_name(pin_name);

	if (!pin) {
		OS_PRINT("ERROR: cannot find pin %s\n", pin_name);
		return -ENXIO;
	}

	pin->pullup_pulldown = pupd;
	return fh_pinctrl_func_select(pin, 0);
}
EXPORT_SYMBOL(fh_pinctrl_spupd);

int fh_pinctrl_sds(char *pin_name, unsigned int ds)
{
	PinCtrl_Pin *pin = NULL;

	if (ds > 7) {
		OS_PRINT("ds val is in [0-7]\n");
		return -EINVAL;
	}

	pin = fh_pinctrl_get_pin_by_name(pin_name);

	if (!pin) {
		OS_PRINT("ERROR: cannot find pin %s\n", pin_name);
		return -ENXIO;
	}

	pin->driving_curr = ds;
	return fh_pinctrl_func_select(pin, 0);
}
EXPORT_SYMBOL(fh_pinctrl_sds);

int fh_pinctrl_set_oe(char *pin_name, unsigned int oe)
{
	PinCtrl_Pin *pin = NULL;

	if (oe > 1) {
		OS_PRINT("oe is 0 or 1\n");
		return -1;
	}

	pin = fh_pinctrl_get_pin_by_name(pin_name);

	if (!pin) {
		OS_PRINT("ERROR: cannot find pin %s\n", pin_name);
		return -2;
	}

	pin->output_enable = oe;
	return fh_pinctrl_func_select(pin, 0);
}
EXPORT_SYMBOL(fh_pinctrl_set_oe);
